package com.shen.nettysocketchat.bus

import androidx.lifecycle.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList

/**
 * 总线
 *
 * 作者：fmtjava
 * github -- https://github.com/fmtjava/LiveDataBus.git
 *
 * 使用方法：
 *  1.1、订阅「普通消息事件」：
 *      LiveDataBus.with<User>("login").observe(this, {
 *          tv_text.text = it.name
 *      })
 *  1.2、发送「普通消息事件」：
 *      LiveDataBus.with<User>("login").postData(user)
 *
 *  2.1、订阅「粘性消息事件」
 *      LiveDataBus.with<String>("name").observeStick(this, {
 *          tv_text.text = it
 *      })
 *  2.2、发送「粘性消息事件」：
 *      LiveDataBus.with<String>("name").postStickData("fmt")
 *
 *
 *
 *
 * HashMap不是线程安全的
 * ConcurrentHashMap具体是怎么实现线程安全的呢，肯定不可能是每个方法加synchronized，那样就变成了HashTable。
 * 从ConcurrentHashMap代码中可以看出，它引入了一个“分段锁”的概念，具体可以理解为把一个大的Map拆分成N个小的HashTable，
 * 根据key.hashCode()来决定把key放到哪个HashTable中。
 * 在ConcurrentHashMap中，就是把Map分成了N个Segment，put和get的时候，都是现根据key.hashCode()算出放到哪个Segment中：
 *
 * 写入时复制（CopyOnWrite）思想
 * 　    写入时复制（CopyOnWrite，简称COW）思想是计算机程序设计领域中的一种优化策略。
 *  其核心思想是，如果有多个调用者（Callers）同时要求相同的资源（如内存或者是磁盘上的数据存储），他们会共同获取相同的指针指向相同的资源，
 *  直到某个调用者视图修改资源内容时，系统才会真正复制一份专用副本（private copy）给该调用者，
 *  而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的（transparently）。
 *  此做法主要的优点是如果调用者没有修改资源，就不会有副本（private copy）被创建，因此多个调用者只是读取操作时可以共享同一份资源。
 */
object LiveDataBus {

    /** 「一个·事件」对应「一个·LiveData」 */
    private val eventMap = ConcurrentHashMap<String, StickLiveData<*>>()
    /** 「一个·事件」对应「多个·页面」 */
    private val lifecycleMap = ConcurrentHashMap<String, CopyOnWriteArrayList<LifecycleOwner>>()
    /** 「一个·页面」对应「多个·事件」 */
    private val eventTypeMap = ConcurrentHashMap<LifecycleOwner, CopyOnWriteArrayList<String>>()

    /** 获取对应「事件」的「LiveData」 */
    fun <T> with(eventName: String): StickLiveData<T> {
        var liveData = eventMap[eventName]

        if (liveData == null) {
            liveData = StickLiveData<T>(eventName)
            eventMap[eventName] = liveData
        }
        return liveData as StickLiveData<T>
    }

    /** 获取「对应事件」关联的「页面集合」*/
    private fun getLifecycleOwners(eventName: String): CopyOnWriteArrayList<LifecycleOwner> {
        var lifecycleOwners = lifecycleMap[eventName]
        if (lifecycleOwners == null) {
            lifecycleOwners = CopyOnWriteArrayList<LifecycleOwner>()
            lifecycleMap[eventName] = lifecycleOwners
        }
        return lifecycleOwners
    }

    /** 添加「事件」对应的「页面」*/
    private fun addLifecycleOwner(eventName: String, lifecycleOwner: LifecycleOwner) {
        val lifecycleOwners = getLifecycleOwners(eventName)
        if (!lifecycleOwners.contains(lifecycleOwner)) {
            lifecycleOwners.add(lifecycleOwner)
        }
    }

    /** 获取「每一个页面」对应的「事件集合」*/
    private fun getEventTypeList(lifecycleOwner: LifecycleOwner): CopyOnWriteArrayList<String> {
        var eventTypeList = eventTypeMap[lifecycleOwner]
        if (eventTypeList == null) {
            eventTypeList = CopyOnWriteArrayList<String>()
            eventTypeMap[lifecycleOwner] = eventTypeList
        }
        return eventTypeList
    }

    /** 为「页面」添加「对应的事件」*/
    private fun addEventType(eventName: String, lifecycleOwner: LifecycleOwner) {
        val eventTypeList = getEventTypeList(lifecycleOwner)
        if (!eventTypeList.contains(eventName)) {
            eventTypeList.add(eventName)
        }
    }


    /** 粘性数据 */
    class StickLiveData<T>(private val eventName: String) : LiveData<T>() {

        internal var mStickData: T? = null          // 粘性·数据
        internal var mVersion = 0                   // 版本号

        private fun setData(data: T) {
            setValue(data)
        }

        fun postData(data: T) {
            postValue(data)
        }

        private fun setStickData(data: T) {
            mStickData = data
            setValue(data)
        }

        fun postStickData(data: T) {
            mStickData = data
            postValue(data)
        }

        override fun setValue(value: T) {
            mVersion++
            super.setValue(value)
        }

        /** 订阅「普通消息事件」[owner]页面 [observer]订阅事件 */
        override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
            observeStick(owner, observer, false)
        }

        /** 订阅「粘性消息事件」[owner]页面 [observer]订阅事件 [stick]是否是粘性事件 */
        fun observeStick(owner: LifecycleOwner, observer: Observer<in T>, stick: Boolean = true) {
            super.observe(owner, StickWarpObserver(this, observer, stick))

            addLifecycleOwner(eventName, owner)                     // 添加「事件」对应的「页面」
            addEventType(eventName, owner)                          // 为「页面」添加「对应的事件」

            owner.lifecycle.addObserver(LifecycleEventObserver { source, event ->
                if (event == Lifecycle.Event.ON_DESTROY) {          // 「页面销毁」时，在「该事件·对应的页面集合中」移除「该页面」
                    getLifecycleOwners(eventName).remove(source)
                    // 「页面销毁」时，获取「该页面·包含的事件集合」，遍历判断「每一种事件类型·对应的页面集合」是否为空，若为空，则移除该事件
                    getEventTypeList(owner).forEach { eventType ->
                        if (getLifecycleOwners(eventType).isEmpty()) {
                            eventMap.remove(eventType)
                        }
                    }
                    eventTypeMap.remove(source)
                }
            })
        }
    }

    /** 「装饰者模式」对「原先的Observer」进行包装 */
    class StickWarpObserver<T>(
        private val stickLiveData: StickLiveData<T>,
        private val observer: Observer<in T>,           // 原先的Observer
        private val stick: Boolean                      // 是否支持「黏性事件」
    ) : Observer<T> {

        // 「创建Observer时」「mLastVersion的默认赋值为·LiveData的Version」, 规避「黏性事件」
        private var mLastVersion = stickLiveData.mVersion

        override fun onChanged(t: T) {
            if (mLastVersion >= stickLiveData.mVersion) {           // 发送粘性事件
                if (stick && stickLiveData.mStickData != null) {
                    observer.onChanged(t)
                }
                return
            }
            mLastVersion = stickLiveData.mVersion
            observer.onChanged(t)
        }
    }
}